
package com.gitee.jmash.rbac.service;

import com.gitee.jmash.common.excel.ExcelImport;
import com.gitee.jmash.common.excel.read.CellValueReader;
import com.gitee.jmash.common.lock.DistributedLock;
import com.gitee.jmash.common.utils.UUIDUtil;
import com.gitee.jmash.core.jaxrs.ParamsValidationException;
import com.gitee.jmash.core.orm.cdi.JpaTenantService;
import com.gitee.jmash.core.transaction.JakartaTransaction;
import com.gitee.jmash.core.utils.FieldMaskUtil;
import com.gitee.jmash.file.client.cdi.FileClient;
import com.gitee.jmash.rbac.entity.ModuleEntity;
import com.gitee.jmash.rbac.entity.PermEntity;
import com.gitee.jmash.rbac.entity.ResourceEntity;
import com.gitee.jmash.rbac.excel.ResourceHeaderImport;
import com.gitee.jmash.rbac.mapper.ResourceMapper;
import jakarta.enterprise.inject.Typed;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import jakarta.transaction.Transactional.TxType;
import jakarta.validation.ValidationException;
import jakarta.validation.executable.ValidateOnExecution;
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import jmash.rbac.protobuf.OperationReq;
import jmash.rbac.protobuf.PermReq;
import jmash.rbac.protobuf.ResourceCreateReq;
import jmash.rbac.protobuf.ResourceImportReq;
import jmash.rbac.protobuf.ResourceModel;
import jmash.rbac.protobuf.ResourceMoveKey;
import jmash.rbac.protobuf.ResourceUpdateReq;

/**
 * 资源表 rbac_resource写服务.
 *
 * @author <a href="mailto:service@crenjoy.com">crenjoy</a>
 */
@Typed(ResourceWrite.class)
@Transactional(TxType.REQUIRED)
@JpaTenantService
@ValidateOnExecution
public class ResourceWriteBean extends ResourceReadBean
    implements ResourceWrite, JakartaTransaction {

  private static Log log = LogFactory.getLog(ResourceWriteBean.class);
  @Inject
  DistributedLock lock;

  @Inject
  ModuleWrite moduleWrite;

  @Override
  @PersistenceContext(unitName = "WriteRbac")
  public void setEntityManager(EntityManager entityManager) {
    this.tem.setEntityManager(entityManager, true);
  }

  @Override
  public ResourceEntity insert(ResourceCreateReq resource) {
    ResourceEntity entity = ResourceMapper.INSTANCE.create(resource);
    // 1.业务校验.
    // 2.仅校验,不执行.
    if (resource.getValidateOnly()) {
      return entity;
    }
    // 3.检查是否重复请求.
    if (!lock.lock(resource.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }

    // 设置ParentId,深度和排序
    resourceDao.insertTree(entity, UUIDUtil.fromString(resource.getParentId()),
        Collections.emptyMap());

    if (UUIDUtil.emptyUUID().equals(entity.getParentId())) {
      // 根目录Url 必须/开头.
      if (!entity.getUrl().startsWith("/")) {
        entity.setUrl("/" + entity.getUrl());
      }
    }
    
    // 4.执行业务(创建人及时间内部处理.)
    // 模块
    ModuleEntity module = moduleDao.find(entity.getModuleId());
    entity.setModuleCode(module.getModuleCode());
    resourceDao.persist(entity);
    // 更新权限列表.
    updatePerms(entity, resource.getOperCodesList());
    //更新是否叶子节点
    resourceDao.updateLeaf();
    return entity;
  }

  /** 更新权限. */
  private void updatePerms(ResourceEntity entity, List<String> operCodes) {
    if (null == entity.getModuleId() || operCodes.isEmpty()) {
      return;
    }
    ModuleEntity module = moduleDao.find(entity.getModuleId());
    String codePrefix = PermEntity.permCodePrefix(module.getModuleCode(), entity.getResourceCode());
    //
    List<PermEntity> perms =
        permDao.findListByReq(PermReq.newBuilder().setLikePermCode(codePrefix).build());
    // 操作编码Set
    Set<String> operCodeSet = operCodes.stream().collect(Collectors.toSet());
    // 移除未勾选权限
    for (PermEntity perm : perms) {
      // 仅考虑前缀权限
      if (perm.getPermCode().startsWith(codePrefix)) {
        String operCode = perm.getPermCode().substring(codePrefix.length());
        if (!operCodeSet.contains(operCode)) {
          //移除角色权限
          rolesPermsDao.removeByPermId(perm.getPermId());
          //移除权限
          permDao.remove(perm);
        }
      }
    }
    Map<String, PermEntity> permMap = perms.stream()
        .collect(Collectors.toMap(PermEntity::getPermCode, obj -> obj, (key1, key2) -> key1));
    // 增加新权限
    String namePrefix = module.getModuleName() + "->" + entity.getResourceName() + "->";
    // 全部操作
    Map<String, String> codeNameMap =
        operationDao.findCodeNameMap(OperationReq.newBuilder().build());
    for (String operCode : operCodeSet) {
      if (codeNameMap.containsKey(operCode) && !permMap.containsKey(codePrefix + operCode)) {
        String name = codeNameMap.get(operCode);
        PermEntity perm = new PermEntity();
        perm.setPermCode(codePrefix + operCode);
        perm.setPermName(namePrefix + name);
        permDao.persist(perm);
      }
    }
  }

  /** 移除资源权限. */
  public void removePerms(ResourceEntity entity) {
    String codePrefix = PermEntity.permCodePrefix(entity.getModuleCode(), entity.getResourceCode());
    //
    List<PermEntity> perms =
        permDao.findListByReq(PermReq.newBuilder().setLikePermCode(codePrefix).build());
    for (PermEntity perm : perms) {
      //移除角色权限
      rolesPermsDao.removeByPermId(perm.getPermId());
      permDao.remove(perm);
    }
  }

  @Override
  public ResourceEntity update(ResourceUpdateReq req) {
    ResourceEntity entity =
        resourceDao.find(UUIDUtil.fromString(req.getResourceId()), req.getValidateOnly());
    if (null == entity) {
      throw new ValidationException("找不到实体:" + req.getResourceId());
    }
    // 无需更新,返回当前数据库数据.
    if (req.getUpdateMask().getPathsCount() == 0) {
      return entity;
    }
    // 移除旧权限
    if (!req.getValidateOnly()
        && !entity.getModuleId().equals(UUIDUtil.fromString(req.getModuleId()))
        || !req.getResourceCode().equals(entity.getResourceCode())) {
      removePerms(entity);
    }
    // 放到FieldMaskUtil.copyMask前
    UUID newParentId = UUIDUtil.fromString(req.getParentId());
    UUID oldParentId = entity.getParentId();
    // 更新掩码属性
    FieldMaskUtil.copyMask(entity, req, req.getUpdateMask());
    // 1.业务校验.
    // 2.仅校验,不执行.
    if (req.getValidateOnly()) {
      return entity;
    }

    // 3.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }

    // 更新ParentId,深度和排序
    resourceDao.updateTree(entity, oldParentId, newParentId, Collections.emptyMap());
    if (UUIDUtil.emptyUUID().equals(entity.getParentId())) {
      // 根目录Url 必须/开头.
      if (!entity.getUrl().startsWith("/")) {
        entity.setUrl("/" + entity.getUrl());
      }
    }
    // 4.执行业务
    // 模块
    ModuleEntity module = moduleDao.find(entity.getModuleId());
    entity.setModuleCode(module.getModuleCode());
    resourceDao.merge(entity);
    // 更新权限列表.
    updatePerms(entity, req.getOperCodesList());
    //更新是否叶子节点
    resourceDao.updateLeaf();
    return entity;
  }

  @Override
  public ResourceEntity delete(UUID entityId) {
    boolean isHaveChild = resourceDao.isHaveChild(entityId);
    if (isHaveChild) {
      throw new ValidationException("资源存在关联");
    } else {
      ResourceEntity entity = resourceDao.removeById(entityId);
      removePerms(entity);
      //更新是否叶子节点
      resourceDao.updateLeaf();
      return entity;
    }    
  }

  @Override
  public Integer batchDelete(Set<UUID> entityIds) {
    int i = 0;
    for (UUID entityId : entityIds) {
      boolean isHaveChild = resourceDao.isHaveChild(entityId);
      if (!isHaveChild) {
        ResourceEntity entity = resourceDao.removeById(entityId);
        removePerms(entity);
        i++;
      }
    }
    //更新是否叶子节点
    resourceDao.updateLeaf();
    return i;
  }

  @Override
  public String importResource(ResourceImportReq req) throws Exception {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    // 文件检查
    File file = FileClient.downloadFile(req.getFileNames());
    if (!file.exists()) {
      return "找不到文件：" + req.getFileNames();
    }

    int success = 0;
    int fail = 0;

    ExcelImport excel = new ExcelImport();

    CellValueReader moduleCellReader =
        ResourceHeaderImport.moduleReader(req, this.moduleWrite, this.moduleDao);
    excel.addHeaders(ResourceHeaderImport.getHeaderImports(moduleCellReader));
    excel.openExcel(file);
    int sheetIndex = 0;
    // 资源模型
    List<ResourceModel> models = excel.importEntity(sheetIndex, ResourceModel.getDefaultInstance());
    // 资源新ID存放.
    Map<String, String> newIdMap = new HashMap<>();
    int i = 0;
    for (ResourceModel model : models) {
      try {
        ResourceEntity entity = resourceDao.find(UUIDUtil.fromString(model.getResourceId()));
        // 替换新ID
        if (newIdMap.containsKey(model.getParentId())) {
          model = model.toBuilder().setParentId(newIdMap.get(model.getParentId())).build();
        }
        if (entity == null) {
          entity = insert(ResourceMapper.INSTANCE.genCreate(model).toBuilder()
              .setRequestId(req.getRequestId() + i++).build());
          // 存放新ID.
          newIdMap.put(model.getResourceId(), entity.getResourceId().toString());
        } else {
          update(ResourceMapper.INSTANCE.genUpdate(model).toBuilder()
              .setUpdateMask(req.getUpdateMask()).setRequestId(req.getRequestId() + i++).build());
        }
        success++;
      } catch (Exception ex) {
        log.error("", ex);
        fail++;
      }
    }
    return String.format("导入成功%d行,失败%d行.<br/>%s", success, fail, excel.getErrorMsg());
  }

  @Override
  public boolean moveOrderBy(ResourceMoveKey req) {
    ResourceEntity entity = findById(UUIDUtil.fromString(req.getResourceId()));
    if (entity == null) {
      throw new ValidationException("找不到实体:" + req.getResourceId());
    }
    return resourceDao.moveOrderBy(req.getUp(), entity, Collections.emptyMap());
  }
}
